from PyQt5.QtCore import QPoint, Qt, pyqtSignal, QPropertyAnimation, QEasingCurve
from PyQt5.QtGui import QCursor, QWheelEvent
from PyQt5.QtWidgets import QScrollArea, QApplication, QScrollBar
from qfluentwidgets import SmoothScroll, SmoothMode


class ScrollArea(QScrollArea):
    def __init__(self, parent=None, trigger: bool = None):
        """

        :param parent: QWidget 父窗口
        :param trigger:  bool  滚动时是否触发进入/离开事件
        """
        super(ScrollArea, self).__init__(parent)
        self.smooth_scroll = SmoothScroll(self, trigger)
        self.smooth_scroll.setSmoothMode(SmoothMode.COSINE)

        if trigger:
            self.verticalScrollBar().valueChanged.connect(self.__fake_move_mouse)

    @staticmethod
    def __fake_move_mouse(self):
        """
        模拟鼠标移动
        :return:
        """
        QCursor.setPos(QCursor.pos() + QPoint(0, 1))
        QApplication.processEvents()
        QCursor.setPos(QCursor.pos() - QPoint(0, 1))

    def wheelEvent(self, a0):
        """
        滚轮事件
        :param a0:
        :return:
        """
        self.smooth_scroll.wheelEvent(a0)


class SmoothScrollBar(QScrollBar):
    """ Smooth scroll bar """

    scrollFinished = pyqtSignal()

    def __init__(self, parent=None):
        QScrollBar.__init__(self, parent)
        self.ani = QPropertyAnimation()
        self.ani.setTargetObject(self)
        self.ani.setPropertyName(b"value")
        self.ani.setEasingCurve(QEasingCurve.OutCubic)
        self.ani.setDuration(500)
        self.__value = self.value()
        self.ani.finished.connect(self.scrollFinished)

    def setValue(self, value: int):
        if value == self.value():
            return

        # stop running animation
        self.ani.stop()
        self.scrollFinished.emit()

        self.ani.setStartValue(self.value())
        self.ani.setEndValue(value)
        self.ani.start()

    def scrollValue(self, value: int):
        """ scroll the specified distance """
        self.__value += value
        self.__value = max(self.minimum(), self.__value)
        self.__value = min(self.maximum(), self.__value)
        self.setValue(self.__value)

    def scrollTo(self, value: int):
        """ scroll to the specified position """
        self.__value = value
        self.__value = max(self.minimum(), self.__value)
        self.__value = min(self.maximum(), self.__value)
        self.setValue(self.__value)

    def resetValue(self, value):
        self.__value = value

    def mousePressEvent(self, e):
        self.ani.stop()
        super().mousePressEvent(e)
        self.__value = self.value()

    def mouseReleaseEvent(self, e):
        self.ani.stop()
        super().mouseReleaseEvent(e)
        self.__value = self.value()

    def mouseMoveEvent(self, e):
        self.ani.stop()
        super().mouseMoveEvent(e)
        self.__value = self.value()


class SmoothScrollArea(QScrollArea):
    """ Smooth scroll area """

    def __init__(self, parent=None, trigger=False):
        """
        Parameters
        ----------
        parent: QWidget
            parent window

        trigger: bool
            whether to trigger the enter/leave event when scrolling
        """
        super().__init__(parent)
        self.vScrollBar = SmoothScrollBar()
        self.vScrollBar.setOrientation(Qt.Vertical)
        self.setVerticalScrollBar(self.vScrollBar)

        if trigger:
            self.verticalScrollBar().valueChanged.connect(self.__fake_move_mouse)

    def wheelEvent(self, e: QWheelEvent):
        self.vScrollBar.scrollValue(-e.angleDelta().y())

    @staticmethod
    def __fake_move_mouse():
        """
        假移动鼠标
        :return:
        """
        QCursor.setPos(QCursor.pos() + QPoint(0, 1))
        QApplication.processEvents()
        QCursor.setPos(QCursor.pos() - QPoint(0, 1))


if __name__ == '__main__':
    import sys
    from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel

    app = QApplication(sys.argv)
    w = QWidget()
    w.setWindowTitle('Smooth Scroll Area')
    w.resize(400, 300)
    layout = QVBoxLayout(w)
    scroll_area = ScrollArea()
    smooth_scroll_bar = SmoothScrollBar()
    smooth_scroll_area = SmoothScrollArea()
    smooth_scroll_area.vScrollBar
    layout.addWidget(QLabel('QScrollArea'))
    layout.addWidget(scroll_area)
    layout.addWidget(QLabel('SmoothScrollBar'))
    layout.addWidget(smooth_scroll_bar)
    layout.addWidget(QLabel('SmoothScrollArea'))
    layout.addWidget(smooth_scroll_area)
    w.show()

    sys.exit(app.exec_())
